package bpipe

import org.junit.Before;
import org.junit.Test

class DependenciesTest {
    
    Properties a,b,c,d
    
    static {
        Runner.initializeLogging("tests")
    }
    
    @Before
    void setUp() {
        
        // 
        // A =>
        //    B 
        //    C =>
        //       D
        println "Setting up"
        a = testFile('a.txt','input1.txt')
        b = testFile('b.txt',a) 
        c = testFile('c.txt',a)
        d = testFile('d.txt',c)
    }
    
    Properties testFile(def name, def input) {
        def a = new Properties()
        // a.outputFile = [ getName: { name }, exists: { true } ] as File
        a.outputFile = new File(name) {
            boolean existsOverride = true;
            boolean exists() { existsOverride }
        }
        
        a.outputPath = name
        a.cleaned = false
        
        if(!(input instanceof List))
            input = [input]
        
        a.inputs = []
        for(def inp in input) {
            if(inp instanceof String)  {
                a.inputs << inp
                a.timestamp = 0
            }
            else {
                a.inputs << inp.outputFile.name
                a.timestamp = Math.max(inp.timestamp + 1, inp.timestamp)
                println " Test output name = $name : time= $a.timestamp"
            }
        }
        if(!a.timestamp)
            a.timestamp = 0
        return a
    }
    
    
    @Test
    void testComputeOutputGraph() {
        
       
       def result = Dependencies.instance.computeOutputGraph([a,b,c,d]).children[0]
       
       println result.dump()
       
       assert result.values*.inputs.flatten() == ['input1.txt']
       
       assert result.children.size() == 2
       
       def bEntry = result.entryFor("b.txt")
       
       assert bEntry != null
       assert bEntry.parents.contains(result)
       assert bEntry.children.isEmpty()
       
       def cEntry = result.entryFor("c.txt")
       def dEntry = result.entryFor("d.txt")
       assert cEntry.parents.contains(result)
       assert cEntry.children.contains( dEntry )
    }
    
    @Test
    void testSpanningDependency() {
        
        def e = testFile('e.txt', [a,d])
        def result = Dependencies.instance.computeOutputGraph([a,b,c,d,e])
        println "Spanning:"
        println result.dump()
        
        def eEntry = result.entryFor('e.txt')
        def aEntry = result.entryFor('a.txt')
        def dEntry = result.entryFor('d.txt')
        assert eEntry.parents.contains(aEntry)
        assert eEntry.parents.contains(dEntry)
    }
    
    @Test
    void testOutputWithNoInputs() {
        
        def e = testFile('e.txt', [])
        def result = Dependencies.instance.computeOutputGraph([a,b,c,d,e])
        println "Graph:"
        println result.dump()
        
        def eEntry = result.entryFor('e.txt')
        def aEntry = result.entryFor('a.txt')
        def dEntry = result.entryFor('d.txt')
        def bEntry = result.entryFor('b.txt')
        def cEntry = result.entryFor('c.txt')
        
        assert !eEntry.parents.contains(aEntry)
        assert !eEntry.parents.contains(dEntry)
        assert !eEntry.parents.contains(bEntry)
        assert !eEntry.parents.contains(cEntry)
        
        assert !eEntry.children.contains(aEntry)
        assert !eEntry.children.contains(dEntry)
        assert !eEntry.children.contains(bEntry)
        assert !eEntry.children.contains(cEntry) 
    }
    
    @Test
    void testEmptyGraph() {
       def result = Dependencies.instance.computeOutputGraph([]) 
       assert result.values.isEmpty()
       assert result.children.isEmpty()
    }
    
    @Test
    void testSingleNode() {
       def result = Dependencies.instance.computeOutputGraph([a]).children[0]
       assert result.values == [a]
       assert result.children.isEmpty()
    }
    
    @Test 
    void testFindLeaves() {
       def result = Dependencies.instance.computeOutputGraph([a,b,c,d]) 
       def leaves = Dependencies.instance.findLeaves(result)
       assert leaves*.values.flatten()*.outputFile*.name.sort() == ['b.txt','d.txt']
    }
    
    @Test
    void testClone() {
       def result = Dependencies.instance.computeOutputGraph([a,b,c,d]) 
       
       println "Unfiltered:\n" + result.dump()
       
        
       GraphEntry cEntry = result.entryFor("c.txt")
       GraphEntry filtered = result.filter("c.txt") 
       GraphEntry cloned = filtered.entryFor("c.txt")
       
       println "Filtered:\n" + filtered.dump()
       
       assert cloned.parents.size() == cEntry.parents.size()
       assert cloned.parents*.values == cEntry.parents*.values
       assert cloned.children.size() == cEntry.children.size()
       assert ! cloned.children.is(cEntry.children)
       assert cloned.parents[0].entryFor('b.txt') == null
    }
    
    @Test 
    void testUpToDate() {
       def result 
       result = Dependencies.instance.computeOutputGraph([a,b,c,d]) 
       assert a.upToDate
       assert b.upToDate
       assert c.upToDate
       assert d.upToDate
       
       // Let's make a be newer than the other files
       def oldts = a.timestamp
       a.timestamp = 100
       result = Dependencies.instance.computeOutputGraph([a,b,c,d]) 
       assert !b.upToDate
       assert !c.upToDate
       assert !d.upToDate
       
       a.timestamp = oldts
       oldts = b.timestamp
       b.timestamp = 100
       result = Dependencies.instance.computeOutputGraph([a,b,c,d]) 
       
       // Nothing depends on B so updating it should not change the fact that 
       // everything else stays up to date
       assert a.upToDate
       assert b.upToDate
       assert c.upToDate
       assert d.upToDate
       
       b.timestamp = oldts
       
       oldts = c.timestamp
       c.timestamp = 100
       result = Dependencies.instance.computeOutputGraph([a,b,c,d]) 
       
       // d depends on c, so it should now be out of date, but a,b,c should be ok
       assert a.upToDate
       assert b.upToDate
       assert c.upToDate
       assert !d.upToDate
       
       c.timestamp = oldts
     }
    
    @Test
    void testLeafOnlyMissing() {
       d.outputFile.existsOverride = false
       def result = Dependencies.instance.computeOutputGraph([a,b,c,d]) 
       
       // now that d is missing, it should not be up to date, because it is a leaf
       assert a.upToDate
       assert b.upToDate
       assert c.upToDate
       assert !d.upToDate
    }
    
    @Test
    void testInternalOnlyMissing() {
       c.outputFile.existsOverride = false
       c.cleaned = true
       
       def result = Dependencies.instance.computeOutputGraph([a,b,c,d]) 
       
       // even though c is missing, it is still up to date, because it is not a leaf
       assert a.upToDate
       assert b.upToDate
       assert c.upToDate
       assert d.upToDate 
    }
    
    @Test
    void testLeafAndInternalMissing() {
       c.outputFile.existsOverride = false
       d.outputFile.existsOverride = false
       def result = Dependencies.instance.computeOutputGraph([a,b,c,d]) 
       
       // now d is missing as well as c, so c becomes out of date too
       assert a.upToDate
       assert b.upToDate
       assert !c.upToDate
       assert !d.upToDate        
    }
    
    @Test
    void testLeafOutOfDateInternalMissing() {
       c.outputFile.existsOverride = false
       d.timestamp = 0
       def result = Dependencies.instance.computeOutputGraph([a,b,c,d]) 
       
       // now d is out of date, as well as c missing, so c becomes out of date too
       assert a.upToDate
       assert b.upToDate
       assert !c.upToDate
       assert !d.upToDate                
    }    
    
    @Test
    void testFirstOutputMissing() {
       a.outputFile.existsOverride = false
       a.cleaned = true
       c.outputFile.existsOverride = false
       c.cleaned = true
       
       def result = Dependencies.instance.computeOutputGraph([a,b,c,d]) 
       
       // now a is missing, but still up to date because all outputs still exist and up to date
       assert a.upToDate
       assert b.upToDate
       assert c.upToDate
       assert d.upToDate                
    }
}